
package com.easyaop.api.around.annotation;

import static net.bytebuddy.matcher.ElementMatchers.named;

import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.easyaop.api.around.CustomTarget;

/**
 * @author lipan
 * @since 2025-02-22
 */
public @interface CustomTargetImpl {

    enum Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder<CustomTarget> {
        INSTANCE;

        private static final MethodDescription.InDefinedShape OPTIONAL;

        public Class<CustomTarget> getHandledType() {
            return CustomTarget.class;
        }

        public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loadable<CustomTarget> annotation,
                                                               MethodDescription source,
                                                               ParameterDescription target,
                                                               Implementation.Target implementationTarget,
                                                               Assigner assigner,
                                                               Assigner.Typing typing) {
            if (target.getType().isPrimitive()) {
                throw new IllegalStateException(target + " uses a primitive type with a @CustomTarget annotation");
            } else if (target.getType().isArray()) {
                throw new IllegalStateException(target + " uses an array type with a @CustomTarget annotation");
            } else if (source.isStatic() && !annotation.getValue(OPTIONAL).resolve(Boolean.class)) {
                return MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
            }
            return new MethodDelegationBinder.ParameterBinding.Anonymous(source.isStatic()
                    ? NullConstant.INSTANCE
                    : new StackManipulation.Compound(MethodVariableAccess.loadThis(),
                    assigner.assign(implementationTarget.getInstrumentedType().asGenericType(), target.getType(),
                            typing)));
        }

        static {
            OPTIONAL = TypeDescription.ForLoadedType.of(CustomTarget.class)
                    .getDeclaredMethods()
                    .filter(named("optional"))
                    .getOnly();
        }
    }
}

